//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this sample source code is subject to the terms of the Microsoft
// license agreement under which you licensed this sample source code. If
// you did not accept the terms of the license agreement, you are not
// authorized to use this sample source code. For the terms of the license,
// please see the license agreement between you and Microsoft or, if applicable,
// see the LICENSE.RTF on your install media or the root of your tools installation.
// THE SAMPLE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES OR INDEMNITIES.
//
// Abstract:
//
//  This wrapper makes a keypad driver available to applications in 
//  the ULDR environment, without needing GWES or Layout Manager.  It doesn't 
//  support repeat-when-held or anything fancy.
// 
//  We handle the details of loading a standard keyboard PDD and DEVICE_LAYOUT.  
//  We present a simple Init/Deinit API to the caller, which passes down a 
//  callback for recieving the VKEYs.
//

#include <keybdpdd.h>
#include <devicelayout.h>
#include <uldrkeypad.h>
#include <winuserm.h>


//----------------------------------------------------------------------------
//
//  Global:  dpCurSettings
//
//  Set debug zones names and initial setting for driver
//
#ifdef DEBUG
#define ZONE_ERROR              DEBUGZONE(0)
#define ZONE_WARNING            DEBUGZONE(1)
#define ZONE_INIT               DEBUGZONE(2)
#define ZONE_FUNCTION           DEBUGZONE(3)
#define ZONE_INFO               DEBUGZONE(4)

DBGPARAM dpCurSettings = {
    L"ULDRKEYPAD", {
        L"ERROR",       L"WARNING",     L"INIT",        L"FUNCTION",
        L"INFO",        L"Undefined",   L"Undefined",   L"Undefined",
        L"Undefined",   L"Undefined",   L"Undefined",   L"Undefined",
        L"Undefined",   L"Undefined",   L"Undefined",   L"Undefined"
    },
    ZONE_ERROR | ZONE_WARNING
};
#endif  // DEBUG

// Link to the keyboard PDD entry point(s)
extern PFN_KEYBD_PDD_ENTRY g_rgpfnPddEntries[];

// Function to obtain a DEVICE_LAYOUT (BSP developer must implement)
extern BOOL UldrKeypadGetDeviceLayout(PDEVICE_LAYOUT pDeviceLayout);


//----------------------------------------------------------------------------
//  Private Data

static DWORD                     g_dwPddIndex;
static KEYBD_PDD                *g_pKeybdPdd;
static PFN_KEYBD_EVENT_CALLBACK  g_pfnKeybdEventCallback;
static DEVICE_LAYOUT             g_deviceLayout;

//----------------------------------------------------------------------------
//  Function Prototypes

BOOL InitKeypadPDD();
BOOL InitKeypadDeviceLayout();

void
KeybdEventCallback(
    UINT    uiPddId,
    UINT32  uiScanCode,
    BOOL    fKeyUp
    );


//----------------------------------------------------------------------------
//
//  Function:  DllMain
//
//
BOOL WINAPI DllMain(HANDLE hinstDll, ULONG Reason, LPVOID Reserved)
{
    switch(Reason) {
    case DLL_PROCESS_ATTACH:
#ifdef RETAIL_DEBUG
        RETAILREGISTERZONES((HMODULE)hinstDll);
#endif
        DEBUGREGISTER((HMODULE)hinstDll);
        DEBUGMSG(ZONE_FUNCTION, (L"ULDRKEYPAD process attach.\r\n"));
        break;
        
    case DLL_PROCESS_DETACH:
        DEBUGMSG(ZONE_FUNCTION, (L"ULDRKEYPAD process detach.\r\n"));
        break;
    }
    return TRUE;
}



//----------------------------------------------------------------------------
//
//  Function:  InitKeypad
//
//  Locate and initialize the keypad's PDD and DEVICE_LAYOUT.
//
BOOL InitKeypad( PFN_KEYBD_EVENT_CALLBACK pfnKeybdEventCallback ) 
{
    BOOL    rval = FALSE;

    DEBUGMSG(ZONE_FUNCTION, (L"+InitKeypad(callback = 0x%08x)\r\n", pfnKeybdEventCallback));

    if (!pfnKeybdEventCallback) {
        ERRORMSG(1, (L"pfnKeybdEventCallback is NULL\r\n"));
        goto cleanUp;
    }
    g_pfnKeybdEventCallback = pfnKeybdEventCallback;

    if (!InitKeypadPDD()) {
        ERRORMSG(1, (L"InitKeypadPDD() failed\r\n"));
        goto cleanUp;
    }
    
    if (!InitKeypadDeviceLayout()) {
        ERRORMSG(1, (L"InitKeypadDeviceLayout() failed\r\n"));
        goto cleanUp;
    }
    rval = TRUE;

cleanUp:
    DEBUGMSG(ZONE_FUNCTION, (L"-InitKeypad(rval = %d)\r\n", rval));
    return rval;
}


//----------------------------------------------------------------------------
//
//  Function:  InitKeypadPDD
//
//  Locate and initialize the keypad's PDD.  It will deliver scancodes to us.
//
static BOOL InitKeypadPDD()
{
    PFN_KEYBD_PDD_ENTRY *ppfnKeybdPddEntry;
    DWORD   dwNumPdds;
    DWORD   dwStatus;
    DWORD   dwSize;
    DWORD   dwType;
    HKEY    hKey = NULL;
    BOOL    rval = FALSE;

    DEBUGMSG(ZONE_FUNCTION, (L"+InitKeypadPDD()\r\n"));

    //--------------------------------------------------------------
    // Determine which PDD corresponds to the keypad
    // (platform may support multiple, e.g. keypad, PS/2, USB)
    //--------------------------------------------------------------
    dwStatus = RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"Drivers\\ULDRKeypad", 0, 0, &hKey);
    if (ERROR_SUCCESS != dwStatus)
    {
        ERRORMSG(1, (L"ULDRKEYPAD: OpenDeviceKey failed with 0x%x\r\n", dwStatus));
        g_dwPddIndex = 0;
    } 
    else 
    {
        dwSize = sizeof(DWORD);
        dwStatus = RegQueryValueEx(hKey, L"PddIndex", NULL, &dwType, (LPBYTE)&g_dwPddIndex, &dwSize);
        if (ERROR_SUCCESS != dwStatus)
        {
            ERRORMSG(1, (L"ULDRKEYPAD: RegQueryValueEx failed with 0x%x\r\n", dwStatus));
            g_dwPddIndex = 0;
        }
        RegCloseKey(hKey);
    }

    //--------------------------------------------------------------
    // Count the PDDs; validate our index
    //--------------------------------------------------------------
    dwNumPdds = 0;
    PREFAST_DEBUGCHK(g_rgpfnPddEntries != NULL);
    ppfnKeybdPddEntry = &g_rgpfnPddEntries[0];
    while (*ppfnKeybdPddEntry != NULL) {
        ++dwNumPdds;
        ++ppfnKeybdPddEntry;
    }

    DEBUGMSG(ZONE_INFO, (L"Number of PDDs:  %d\r\n", dwNumPdds));
    DEBUGMSG(ZONE_INFO, (L"Keypad at index: %d\r\n", g_dwPddIndex));
    if (g_dwPddIndex >= dwNumPdds) {
        ERRORMSG(1, (_T("No PDD found at index %d; check your registry\r\n"), g_dwPddIndex));
        goto cleanUp;
    }
    ppfnKeybdPddEntry = &g_rgpfnPddEntries[ g_dwPddIndex ];


    //--------------------------------------------------------------
    // Init the keypad PDD
    //--------------------------------------------------------------
    rval = (*g_rgpfnPddEntries[ g_dwPddIndex ])
        (g_dwPddIndex, KeybdEventCallback, &g_pKeybdPdd);

    if (FALSE == rval) {
        ERRORMSG(1, (_T("Keypad: PDD initialization failed\r\n")));
        goto cleanUp;
    }

    DEBUGCHK(g_pKeybdPdd != NULL);
    DEBUGCHK(g_pKeybdPdd->pszName != NULL);
    DEBUGMSG(ZONE_INIT, (_T("Initialized PDD [%s] (Mask %u)\r\n"),
        g_pKeybdPdd->pszName, g_pKeybdPdd->wPddMask));

    rval = TRUE;    

cleanUp:
    DEBUGMSG(ZONE_FUNCTION, (L"-InitKeypadPDD(rval = %d)\r\n", rval));
    return rval;
}



//----------------------------------------------------------------------------
//
//  Function:  InitKeypadDeviceLayout
//
//  Request the keypad's DEVICE_LAYOUT.  
//
//  This need only provide a scancode-to-vkey mapping table for: 
//      VK_UP
//      VK_DOWN 
//      VK_ENTER
//      VK_OFF (power button)
//
//  It need not provide an implementation of PFN_KEYBD_REMAP.
//
//
static BOOL InitKeypadDeviceLayout()
{
    BOOL    rval = FALSE;
    DEBUGMSG(ZONE_FUNCTION, (L"+InitKeypadDeviceLayout()\r\n"));

    g_deviceLayout.dwSize = sizeof(DEVICE_LAYOUT);
    rval = UldrKeypadGetDeviceLayout( &g_deviceLayout );

    if (!rval) {
        ERRORMSG(1, (L"Failed to obtain DEVICE_LAYOUT; can't map scancodes to VKEYs\r\n"));   
        goto cleanUp;
    }
    
    DEBUGMSG(ZONE_INFO, (L"DEVICE_LAYOUT.dwSize         = 0x%x\r\n", g_deviceLayout.dwSize));
    DEBUGMSG(ZONE_INFO, (L"DEVICE_LAYOUT.wPddMask       = 0x%x\r\n", g_deviceLayout.wPddMask));
    DEBUGMSG(ZONE_INFO, (L"DEVICE_LAYOUT.rgpscvk        = 0x%x\r\n", g_deviceLayout.rgpscvk));
    DEBUGMSG(ZONE_INFO, (L"DEVICE_LAYOUT.cpscvk         = 0x%x\r\n", g_deviceLayout.cpscvk));
    DEBUGMSG(ZONE_INFO, (L"DEVICE_LAYOUT.pfnRemapKey    = 0x%x\r\n", g_deviceLayout.pfnRemapKey));

cleanUp:
    DEBUGMSG(ZONE_FUNCTION, (L"-InitKeypadDeviceLayout(rval = %d)\r\n", rval));
    return rval;
}



//----------------------------------------------------------------------------
//
//  Function:  DeinitKeypad
//
BOOL DeinitKeypad()
{
    DEBUGMSG(ZONE_FUNCTION, (L"+DeinitKeypad()\r\n"));

    // We must power down the keypad or otherwise prevent interrupts 
    // before uldrkeypad.dll unloads.

    DEBUGMSG(ZONE_INFO, (L"Powering down the keypad.\r\n"));
    if (g_pKeybdPdd->pfnPowerHandler) 
    {
        g_pKeybdPdd->pfnPowerHandler( g_dwPddIndex, TRUE );
    } 
    else 
    {
        ERRORMSG(1, (L"Keypad PDD does not support power down!  Spin forever.\r\n"));
        while(1);
    }
    
    DEBUGMSG(ZONE_FUNCTION, (L"-DeinitKeypad()\r\n"));
    
    return TRUE;
}



//----------------------------------------------------------------------------
//
// Function: KeybdEventCallback
//
// Callback function the PDDs use to send events (scancodes). 
// Converts scancodes to VKEYs and delivers them to the calling app.
//
static void
KeybdEventCallback(
    UINT    uiPddId,
    UINT32  uiScanCode,
    BOOL    fKeyUp
    )
{
    ScanCodeToVKeyData *pTable = NULL;
    UINT32              vkey   = 0;
    BOOL                fFound = FALSE;
    
    DEBUGMSG(ZONE_FUNCTION, (L"+KeybdEventCallback()\r\n"));

    //--------------------------------------------------------------
    // If they ULDR keypad driver is reusing a standard driver's 
    // DEVICE_LAYOUT, it may have given us multiple mapping tables.  
    // Find the one that contains our scan code.
    //--------------------------------------------------------------
    for (UINT i = 0; i < g_deviceLayout.cpscvk; i++) {
        pTable = g_deviceLayout.rgpscvk[i];
        if ( (pTable->uMin <= uiScanCode) && (uiScanCode <= pTable->uMax) ) {
            fFound = TRUE;
            break;
        }
    }
    
    if (!fFound) {
        DEBUGMSG(ZONE_INFO, (L"Scan code 0x%x not present in DEVICE_LAYOUT; ignoring\r\n", uiScanCode));   
        return;
    }
    
    vkey = pTable->puScanCodeToVKey[ uiScanCode - pTable->uMin ];
    g_pfnKeybdEventCallback( vkey,   fKeyUp );

    switch (vkey) {
        case VK_UP:
        case VK_DOWN:
        case VK_LEFT:
        case VK_RIGHT:
            // According to MSDN the d-pad should send VK_DPAD (VK_F21) 
            // after a directional VKEY.
            g_pfnKeybdEventCallback( VK_DPAD, fKeyUp );
            break;
        case VK_RETURN:
            // According to MSDN the d-pad, when depressed, should send 
            // both VK_RETURN and VK_ACTION
            g_pfnKeybdEventCallback( VK_ACTION, fKeyUp );
            break;
    }
 
    DEBUGMSG(ZONE_INFO, (L"PDD %d: scancode 0x%x, VKEY = 0x%x\r\n", 
        uiPddId, uiScanCode, vkey));
    DEBUGMSG(ZONE_FUNCTION, (L"-KeybdEventCallback()\r\n"));

    return;    
}

